home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / system-config-printer / authconn.py < prev    next >
Encoding:
Python Source  |  2010-09-28  |  16.6 KB  |  467 lines

  1. #!/usr/bin/env python
  2.  
  3. ## Copyright (C) 2007, 2008, 2009, 2010 Red Hat, Inc.
  4. ## Author: Tim Waugh <twaugh@redhat.com>
  5.  
  6. ## This program is free software; you can redistribute it and/or modify
  7. ## it under the terms of the GNU General Public License as published by
  8. ## the Free Software Foundation; either version 2 of the License, or
  9. ## (at your option) any later version.
  10.  
  11. ## This program is distributed in the hope that it will be useful,
  12. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. ## GNU General Public License for more details.
  15.  
  16. ## You should have received a copy of the GNU General Public License
  17. ## along with this program; if not, write to the Free Software
  18. ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  19.  
  20. import threading
  21. import cups
  22. import cupspk
  23. import gobject
  24. import gtk
  25. import os
  26. from errordialogs import *
  27. from debug import *
  28.  
  29. _ = lambda x: x
  30. N_ = lambda x: x
  31. def set_gettext_function (fn):
  32.     global _
  33.     _ = fn
  34.  
  35. class AuthDialog(gtk.Dialog):
  36.     AUTH_FIELD={'username': N_("Username:"),
  37.                 'password': N_("Password:"),
  38.                 'domain': N_("Domain:")}
  39.  
  40.     def __init__ (self, title=None, parent=None,
  41.                   flags=gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR,
  42.                   buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
  43.                            gtk.STOCK_OK, gtk.RESPONSE_OK),
  44.                   auth_info_required=['username', 'password'],
  45.                   allow_remember=False):
  46.         if title == None:
  47.             title = _("Authentication")
  48.         gtk.Dialog.__init__ (self, title, parent, flags, buttons)
  49.         self.auth_info_required = auth_info_required
  50.         self.set_default_response (gtk.RESPONSE_OK)
  51.         self.set_border_width (6)
  52.         self.set_resizable (False)
  53.         hbox = gtk.HBox (False, 12)
  54.         hbox.set_border_width (6)
  55.         image = gtk.Image ()
  56.         image.set_from_stock (gtk.STOCK_DIALOG_AUTHENTICATION,
  57.                               gtk.ICON_SIZE_DIALOG)
  58.         image.set_alignment (0.0, 0.0)
  59.         hbox.pack_start (image, False, False, 0)
  60.         vbox = gtk.VBox (False, 12)
  61.         self.prompt_label = gtk.Label ()
  62.         vbox.pack_start (self.prompt_label, False, False, 0)
  63.  
  64.         num_fields = len (auth_info_required)
  65.         table = gtk.Table (num_fields, 2)
  66.         table.set_row_spacings (6)
  67.         table.set_col_spacings (6)
  68.  
  69.         self.field_entry = []
  70.         for i in range (num_fields):
  71.             field = auth_info_required[i]
  72.             label = gtk.Label (_(self.AUTH_FIELD.get (field, field)))
  73.             label.set_alignment (0, 0.5)
  74.             table.attach (label, 0, 1, i, i + 1)
  75.             entry = gtk.Entry ()
  76.             entry.set_visibility (field != 'password')
  77.             table.attach (entry, 1, 2, i, i + 1, 0, 0)
  78.             self.field_entry.append (entry)
  79.  
  80.         self.field_entry[num_fields - 1].set_activates_default (True)
  81.         vbox.pack_start (table, False, False, 0)
  82.         hbox.pack_start (vbox, False, False, 0)
  83.         self.vbox.pack_start (hbox)
  84.  
  85.         if allow_remember:
  86.             cb = gtk.CheckButton (_("Remember password"))
  87.             cb.set_active (False)
  88.             vbox.pack_start (cb)
  89.             self.remember_checkbox = cb
  90.  
  91.         self.vbox.show_all ()
  92.  
  93.     def set_prompt (self, prompt):
  94.         self.prompt_label.set_markup ('<span weight="bold" size="larger">' +
  95.                                       prompt + '</span>')
  96.         self.prompt_label.set_use_markup (True)
  97.         self.prompt_label.set_alignment (0, 0)
  98.         self.prompt_label.set_line_wrap (True)
  99.  
  100.     def set_auth_info (self, auth_info):
  101.         for i in range (len (self.field_entry)):
  102.             self.field_entry[i].set_text (auth_info[i])
  103.  
  104.     def get_auth_info (self):
  105.         return map (lambda x: x.get_text (), self.field_entry)
  106.  
  107.     def get_remember_password (self):
  108.         try:
  109.             return self.remember_checkbox.get_active ()
  110.         except AttributeError:
  111.             return False
  112.  
  113.     def field_grab_focus (self, field):
  114.         i = self.auth_info_required.index (field)
  115.         self.field_entry[i].grab_focus ()
  116.  
  117. class Connection:
  118.     def __init__ (self, parent=None, try_as_root=True, lock=False,
  119.                   host=None, port=None, encryption=None):
  120.         if host != None:
  121.             cups.setServer (host)
  122.         if port != None:
  123.             cups.setPort (port)
  124.         if encryption != None:
  125.             cups.setEncryption (encryption)
  126.  
  127.         self._use_password = ''
  128.         self._parent = parent
  129.         self._try_as_root = try_as_root
  130.         self._use_user = cups.getUser ()
  131.         self._server = cups.getServer ()
  132.         self._port = cups.getPort()
  133.         self._encryption = cups.getEncryption ()
  134.         self._prompt_allowed = True
  135.         self._operation_stack = []
  136.         self._lock = lock
  137.         self._gui_event = threading.Event ()
  138.         self._connect ()
  139.  
  140.     def _begin_operation (self, operation):
  141.         self._operation_stack.append (operation)
  142.  
  143.     def _end_operation (self):
  144.         self._operation_stack.pop ()
  145.  
  146.     def _get_prompt_allowed (self, ):
  147.         return self._prompt_allowed
  148.  
  149.     def _set_prompt_allowed (self, allowed):
  150.         self._prompt_allowed = allowed
  151.  
  152.     def _set_lock (self, whether):
  153.         self._lock = whether
  154.  
  155.     def _connect (self, allow_pk=True):
  156.         cups.setUser (self._use_user)
  157.  
  158.         self._use_pk = (allow_pk and
  159.                         (self._server[0] == '/' or self._server == 'localhost')
  160.                         and os.getuid () != 0)
  161.         if self._use_pk:
  162.             create_object = cupspk.Connection
  163.         else:
  164.             create_object = cups.Connection
  165.  
  166.         self._connection = create_object (host=self._server,
  167.                                             port=self._port,
  168.                                             encryption=self._encryption)
  169.  
  170.         if self._use_pk:
  171.             self._connection.set_parent(self._parent)
  172.  
  173.         self._user = self._use_user
  174.         debugprint ("Connected as user %s" % self._user)
  175.         methodtype_lambda = type (self._connection.getPrinters)
  176.         methodtype_real = type (self._connection.addPrinter)
  177.         for fname in dir (self._connection):
  178.             if fname[0] == '_':
  179.                 continue
  180.             fn = getattr (self._connection, fname)
  181.             if not type (fn) in [methodtype_lambda, methodtype_real]:
  182.                 continue
  183.             setattr (self, fname, self._make_binding (fname, fn))
  184.  
  185.     def _make_binding (self, fname, fn):
  186.         return lambda *args, **kwds: self._authloop (fname, fn, *args, **kwds)
  187.  
  188.     def _authloop (self, fname, fn, *args, **kwds):
  189.         self._passes = 0
  190.         c = self._connection
  191.         retry = False
  192.         while True:
  193.             try:
  194.                 if self._perform_authentication () == 0:
  195.                     break
  196.  
  197.                 if c != self._connection:
  198.                     # We have reconnected.
  199.                     fn = getattr (self._connection, fname)
  200.                     c = self._connection
  201.  
  202.                 cups.setUser (self._use_user)
  203.  
  204.                 result = fn.__call__ (*args, **kwds)
  205.  
  206.                 if fname == 'adminGetServerSettings':
  207.                     # Special case for a rubbish bit of API.
  208.                     if result == {}:
  209.                         # Authentication failed, but we aren't told that.
  210.                         raise cups.IPPError (cups.IPP_NOT_AUTHORIZED, '')
  211.                 break
  212.             except cups.IPPError, (e, m):
  213.                 if self._use_pk and m == 'pkcancel':
  214.                     raise cups.IPPError (0, _("Operation canceled"))
  215.                 if not self._cancel and (e == cups.IPP_NOT_AUTHORIZED or
  216.                                          e == cups.IPP_FORBIDDEN):
  217.                     self._failed (e == cups.IPP_FORBIDDEN)
  218.                 elif not self._cancel and e == cups.IPP_SERVICE_UNAVAILABLE:
  219.                     if self._lock:
  220.                         self._gui_event.clear ()
  221.                         gobject.timeout_add (1, self._ask_retry_server_error, m)
  222.                         self._gui_event.wait ()
  223.                     else:
  224.                         self._ask_retry_server_error (m)
  225.  
  226.                     if self._retry_response == gtk.RESPONSE_OK:
  227.                         debugprint ("retrying operation...")
  228.                         retry = True
  229.                         self._passes -= 1
  230.                         self._has_failed = True
  231.                     else:
  232.                         self._cancel = True
  233.                         raise
  234.                 else:
  235.                     if self._cancel and not self._cannot_auth:
  236.                         raise cups.IPPError (0, _("Operation canceled"))
  237.  
  238.                     raise
  239.             except cups.HTTPError, (s,):
  240.                 if not self._cancel:
  241.                     self._failed (s == cups.HTTP_FORBIDDEN)
  242.                 else:
  243.                     raise
  244.  
  245.         return result
  246.  
  247.     def _ask_retry_server_error (self, message):
  248.         if self._lock:
  249.             gtk.gdk.threads_enter ()
  250.  
  251.         try:
  252.             msg = _("CUPS server error (%s)") % self._operation_stack[0]
  253.         except IndexError:
  254.             msg = _("CUPS server error")
  255.  
  256.         d = gtk.MessageDialog (self._parent,
  257.                                gtk.DIALOG_MODAL |
  258.                                gtk.DIALOG_DESTROY_WITH_PARENT,
  259.                                gtk.MESSAGE_ERROR,
  260.                                gtk.BUTTONS_NONE,
  261.                                msg)
  262.                                
  263.         d.format_secondary_text (_("There was an error during the "
  264.                                    "CUPS operation: '%s'." % message))
  265.         d.add_buttons (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
  266.                        _("Retry"), gtk.RESPONSE_OK)
  267.         d.set_default_response (gtk.RESPONSE_OK)
  268.         if self._lock:
  269.             d.connect ("response", self._on_retry_server_error_response)
  270.             gtk.gdk.threads_leave ()
  271.         else:
  272.             self._retry_response = d.run ()
  273.             d.destroy ()
  274.  
  275.     def _on_retry_server_error_response (self, dialog, response):
  276.         self._retry_response = response
  277.         dialog.destroy ()
  278.         self._gui_event.set ()
  279.  
  280.     def _failed (self, forbidden=False):
  281.         self._has_failed = True
  282.         self._forbidden = forbidden
  283.  
  284.     def _password_callback (self, prompt):
  285.         debugprint ("Got password callback")
  286.         if self._cancel or self._auth_called:
  287.             return ''
  288.  
  289.         self._auth_called = True
  290.         self._prompt = prompt
  291.         return self._use_password
  292.  
  293.     def _perform_authentication (self):
  294.         self._passes += 1
  295.  
  296.         debugprint ("Authentication pass: %d" % self._passes)
  297.         if self._passes == 1:
  298.             # Haven't yet tried the operation.  Set the password
  299.             # callback and return > 0 so we try it for the first time.
  300.             self._has_failed = False
  301.             self._forbidden = False
  302.             self._auth_called = False
  303.             self._cancel = False
  304.             self._cannot_auth = False
  305.             self._dialog_shown = False
  306.             cups.setPasswordCB (self._password_callback)
  307.             debugprint ("Authentication: password callback set")
  308.             return 1
  309.  
  310.         debugprint ("Forbidden: %s" % self._forbidden)
  311.         if not self._has_failed:
  312.             # Tried the operation and it worked.  Return 0 to signal to
  313.             # break out of the loop.
  314.             debugprint ("Authentication: Operation successful")
  315.             return 0
  316.  
  317.         # Reset failure flag.
  318.         self._has_failed = False
  319.  
  320.         if self._passes >= 2:
  321.             # Tried the operation without a password and it failed.
  322.             if (self._try_as_root and
  323.                 self._user != 'root' and
  324.                 (self._server[0] == '/' or self._forbidden)):
  325.                 # This is a UNIX domain socket connection so we should
  326.                 # not have needed a password (or it is not a UDS but
  327.                 # we got an HTTP_FORBIDDEN response), and so the
  328.                 # operation must not be something that the current
  329.                 # user is authorised to do.  They need to try as root,
  330.                 # and supply the password.  However, to get the right
  331.                 # prompt, we need to try as root but with no password
  332.                 # first.
  333.                 debugprint ("Authentication: Try as root")
  334.                 self._use_user = 'root'
  335.                 self._auth_called = False
  336.                 try:
  337.                     self._connect (allow_pk=False)
  338.                 except RuntimeError:
  339.                     raise cups.IPPError (cups.IPP_SERVICE_UNAVAILABLE,
  340.                                          'server-error-service-unavailable')
  341.  
  342.                 return 1
  343.  
  344.         if not self._prompt_allowed:
  345.             debugprint ("Authentication: prompting not allowed")
  346.             self._cancel = True
  347.             return 1
  348.  
  349.         if not self._auth_called:
  350.             # We aren't even getting a chance to supply credentials.
  351.             debugprint ("Authentication: giving up")
  352.             self._cancel = True
  353.             self._cannot_auth = True
  354.             return 1
  355.  
  356.         # Reset the flag indicating whether we were given an auth callback.
  357.         self._auth_called = False
  358.  
  359.         # If we're previously prompted, explain why we're prompting again.
  360.         if self._dialog_shown:
  361.             if self._lock:
  362.                 self._gui_event.clear ()
  363.                 gobject.timeout_add (1, self._show_not_authorized_dialog)
  364.                 self._gui_event.wait ()
  365.             else:
  366.                 self._show_not_authorized_dialog ()
  367.  
  368.         if self._lock:
  369.             self._gui_event.clear ()
  370.             gobject.timeout_add (1, self._perform_authentication_with_dialog)
  371.             self._gui_event.wait ()
  372.         else:
  373.             self._perform_authentication_with_dialog ()
  374.  
  375.         if self._cancel:
  376.             debugprint ("cancelled")
  377.             return -1
  378.  
  379.         cups.setUser (self._use_user)
  380.         debugprint ("Authentication: Reconnect")
  381.         try:
  382.             self._connect (allow_pk=False)
  383.         except RuntimeError:
  384.             raise cups.IPPError (cups.IPP_SERVICE_UNAVAILABLE,
  385.                                  'server-error-service-unavailable')
  386.  
  387.         return 1
  388.  
  389.     def _show_not_authorized_dialog (self):
  390.         if self._lock:
  391.             gtk.gdk.threads_enter ()
  392.         d = gtk.MessageDialog (self._parent,
  393.                                gtk.DIALOG_MODAL |
  394.                                gtk.DIALOG_DESTROY_WITH_PARENT,
  395.                                gtk.MESSAGE_ERROR,
  396.                                gtk.BUTTONS_CLOSE)
  397.         d.set_title (_("Not authorized"))
  398.         d.set_markup ('<span weight="bold" size="larger">' +
  399.                       _("Not authorized") + '</span>\n\n' +
  400.                       _("The password may be incorrect."))
  401.         if self._lock:
  402.             d.connect ("response", self._on_not_authorized_dialog_response)
  403.             d.show_all ()
  404.             d.show_now ()
  405.             gtk.gdk.threads_leave ()
  406.         else:
  407.             d.run ()
  408.             d.destroy ()
  409.  
  410.     def _on_not_authorized_dialog_response (self, dialog, response):
  411.         self._gui_event.set ()
  412.         dialog.destroy ()
  413.  
  414.     def _perform_authentication_with_dialog (self):
  415.         if self._lock:
  416.             gtk.gdk.threads_enter ()
  417.  
  418.         # Prompt.
  419.         if len (self._operation_stack) > 0:
  420.             try:
  421.                 title = _("Authentication (%s)") % self._operation_stack[0]
  422.             except IndexError:
  423.                 title = _("Authentication")
  424.  
  425.             d = AuthDialog (title=title,
  426.                             parent=self._parent)
  427.         else:
  428.             d = AuthDialog (parent=self._parent)
  429.  
  430.         d.set_prompt (self._prompt)
  431.         d.set_auth_info ([self._use_user, ''])
  432.         d.field_grab_focus ('password')
  433.         d.set_keep_above (True)
  434.         d.show_all ()
  435.         d.show_now ()
  436.         self._dialog_shown = True
  437.         if self._lock:
  438.             d.connect ("response", self._on_authentication_response)
  439.             gtk.gdk.threads_leave ()
  440.         else:
  441.             response = d.run ()
  442.             self._on_authentication_response (d, response)
  443.  
  444.     def _on_authentication_response (self, dialog, response):
  445.         (self._use_user,
  446.          self._use_password) = dialog.get_auth_info ()
  447.         dialog.destroy ()
  448.  
  449.         if (response == gtk.RESPONSE_CANCEL or
  450.             response == gtk.RESPONSE_DELETE_EVENT):
  451.             self._cancel = True
  452.  
  453.         if self._lock:
  454.             self._gui_event.set ()
  455.  
  456. if __name__ == '__main__':
  457.     # Test it out.
  458.     gtk.gdk.threads_init ()
  459.     from timedops import TimedOperation
  460.     set_debugging (True)
  461.     c = TimedOperation (Connection, args=(None,)).run ()
  462.     debugprint ("Connected")
  463.     c._set_lock (True)
  464.     print TimedOperation (c.getFile,
  465.                           args=('/admin/conf/cupsd.conf',
  466.                                 '/dev/stdout')).run ()
  467.